﻿using BrightIdeasSoftware;
using ComponentFactory.Krypton.Toolkit;
using ModelHasher;
using ModelHasher.Framework;
using ModelHasher.Utils;
using StableDiffusionTools.Controls.KryptonHelper;
using StableDiffusionTools.Forms;
using StableDiffusionTools.Framework;
using StableDiffusionTools.Imaging;
using StableDiffusionTools.Themes;
using StableDiffusionTools.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Data;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using VAELinker.Entry;
using VAELinker.Framework;
using VAELinker.Renderers;
using VAELinker.Utils;

namespace VAELinker.Forms
{
    [Export(typeof(IForm))]
    [ExportMetadata("Name", "VaeLinker")]
    [ExportMetadata("Image", "vaelinker32")]
    public partial class VaeLinkerForm : KryptonForm, IForm
    {
        public bool Busy { get; protected set; }
        private List<EntryVAE> vaeEntries = new List<EntryVAE>();
        private List<EntryModel> modelEntries = new List<EntryModel>();
        private TypedObjectListView<EntryModel> tolvFiles;
        private SettingsManager<VAELinkerSettings> settingsMan = new SettingsManager<VAELinkerSettings>(nameof(VAELinkerSettings), createBackups: false);
        private bool initializing = false;
        private VAELinkerSettings settings;
        private string currentPath;
        private OLVColumn lastColumn;
        private SortOrder lastSortOrder = SortOrder.Ascending;
        private bool closing = false;
        private object addremoveLock = new object();
        private bool resolveMissingHashes = true;
        private ConcurrentQueue<Tuple<EntryModel, bool>> hashingQueue = new ConcurrentQueue<Tuple<EntryModel, bool>>();
        private object hashingLock = new object();
        private StateImage<bool> pathState = new StateImage<bool>();

        public VaeLinkerForm()
        {
            InitializeComponent();
            GlobalTheme.Init();
            buttonSpecTheme.KryptonContextMenu = new KryptonThemeContextMenu();

            Icon = Properties.Resources.icon;
            pathState[true] = StableDiffusionTools.Properties.Resources.statusok16;
            pathState[false] = StableDiffusionTools.Properties.Resources.statusfail16;

            settings = settingsMan.LoadSettings();
            imageListSmall.Images.Add("Symlinked VAE", Properties.Resources.bullet_green16);
            imageListSmall.Images.Add("Has VAE", Properties.Resources.bullet_purple16);
            imageListSmall.Images.Add("No VAE", Properties.Resources.bullet_black16);

            createSymLinkedVAEToolStripMenuItem.Image = Properties.Resources.link16;
            deleteSymLinkToolStripMenuItem.Image = Properties.Resources.brokenlink16;
            copyToolStripMenuItem.Image = Properties.Resources.copy16;
            hashToolStripMenuItem.Image = Properties.Resources.hash16;

            kryptonBTNBrowse.StateCommon.Back.Image = StableDiffusionTools.Properties.Resources.folder16;

            tolvFiles = new TypedObjectListView<EntryModel>(olvFiles);

            string symlinkGetter(EntryModel model) => (model == null) ? "No VAE" : model.HasSymLinkedVAE ? "Symlinked VAE" : (model.HasNonSymLinkedVAE ? "Has VAE" : "No VAE");

            olvColumnSymLink.AspectToStringConverter = (e) => string.Empty;
            olvColumnSymLink.GroupKeyToTitleConverter = (e) => e.ToString();
            TypedColumn<EntryModel> tSymLink = new TypedColumn<EntryModel>(olvColumnSymLink)
            {
                AspectGetter = symlinkGetter,
                ImageGetter = symlinkGetter,
                GroupKeyGetter = symlinkGetter,
            };

            tolvFiles.GetColumn("Path").AspectGetter += (o) => o?.PathName;
            olvColumnPath.Renderer = settings.ColorPaths ? new ColoredTextRenderer() { UseGdiTextRendering = false } : new BaseRenderer() { UseGdiTextRendering = false };

            tolvFiles.GetColumn("Name").AspectGetter += (o) => o?.FileName;
            //tolvFiles.GetColumn("Name").AspectPutter += (o, e) =>
            //{
            //    if (e is string newVal)
            //    {
            //        o.Rename(newVal);
            //    }
            //};
            olvColumnName.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            tolvFiles.GetColumn("Size").AspectGetter += (o) => o?.Size;
            olvColumnSize.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            tolvFiles.GetColumn("Hash").AspectGetter += (o) => 
            {
                string foundHash = "";
                if (o != null)
                {
                    if(o.Hash == null)
                    {
                        string pathAndFileName = o.PathName + o.FileName;
                        if (ModelHasherState.State.TryGetModelHashByPathAndName(pathAndFileName, out ModelHash modelHash))
                        {
                            foundHash = modelHash.ShortHash;
                        }
                        else if (resolveMissingHashes && ModelHasherState.State.TryGetModelHashByName(o.FileName, out modelHash))
                        {
                            if (!ModelHasherState.ModelExistsOnDisk(o.RootDirectory + '\\' + modelHash.Name) && ModelHasherState.ModelExistsOnDisk(o.RootDirectory + '\\' + pathAndFileName))
                            {
                                DialogResult result = KryptonMessageBox.Show(this, $"Did this model move?\nold:\t{modelHash.Name}\nnew:\t{pathAndFileName}\n\nYes to update the cache", "Model moved?!", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation);
                                if (result == DialogResult.Yes)
                                {
                                    modelHash.Name = pathAndFileName;
                                    foundHash = modelHash.ShortHash;
                                }
                                else if (result == DialogResult.Cancel)
                                {
                                    resolveMissingHashes = false;
                                }
                            }
                        }
                        o.Hash = foundHash;
                    }
                    foundHash = o.Hash;
                }

                return foundHash;
            };
            olvColumnHash.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            tolvFiles.GetColumn("VAE").AspectGetter += (o) => o?.LogicalVAEName;
            olvColumnVAE.Renderer = new BaseRenderer() { UseGdiTextRendering = false };

            //olvFiles.CellEditActivation = ObjectListView.CellEditActivateMode.DoubleClick;
            //olvFiles.CellEditStarting += (s, e) =>
            //{
            //    if (e.Column == olvColumnName)
            //    {
            //        if (e.Control is TextBox tb)
            //        {
            //            tb.Bounds = e.CellBounds;
            //        }
            //    }
            //};

            //olvFiles.CellEditFinishing += (s, e) =>
            //{
            //    if (e.Column == olvColumnName)
            //    {
            //        if (!e.Cancel && e.RowObject is EntryModel m)
            //        {
            //            //validate path and arguments
            //            if (!m.Rename(e.NewValue as string))
            //            {
            //                e.Cancel = true;
            //            }
            //        }
            //    }
            //};

            ctxFiles.KeyDown += (s, e) =>
            {
                if(e.KeyCode == Keys.ShiftKey)
                {
                    copyToolStripMenuItem.Text = "Copy (+ Hash)";
                    hashToolStripMenuItem.Text = "Hash (Forced)";
                }
            };

            ctxFiles.KeyUp += (s, e) =>
            {
                if (e.KeyCode == Keys.ShiftKey)
                {
                    copyToolStripMenuItem.Text = "Copy";
                    hashToolStripMenuItem.Text = "Hash";
                }
            };

            groupToolStripMenuItem.DropDown.KeyDown += (s, e) =>
            {
                if (e.KeyCode == Keys.ShiftKey)
                {
                    copyToolStripMenuItem.Text = "Copy (+ Hash)";
                    hashToolStripMenuItem.Text = "Hash (Forced)";
                }
            };

            groupToolStripMenuItem.DropDown.KeyUp += (s, e) =>
            {
                if (e.KeyCode == Keys.ShiftKey)
                {
                    copyToolStripMenuItem.Text = "Copy";
                    hashToolStripMenuItem.Text = "Hash";
                }
            };

            olvFiles.BeforeSorting += (s, e) => 
            {
#if DEBUG
                System.Diagnostics.Debug.WriteLine(new string('-', 20));
                System.Diagnostics.Debug.WriteLine($"LastSort: {olvFiles?.LastSortColumn?.Text ?? "null"} {olvFiles.LastSortOrder}\nToSort: {e.ColumnToSort?.Text ?? "null"} {e.SortOrder}\nSecondaryToSort: {e.SecondaryColumnToSort?.Text ?? "null"} {e.SecondarySortOrder}"); 
#endif
                if(e.ColumnToSort != null)
                {
                    OLVColumn secondarySortCandidate = lastColumn ?? olvColumnName;
                    if (secondarySortCandidate != e.ColumnToSort)
                    {
                        e.SecondaryColumnToSort = secondarySortCandidate;
                        e.SecondarySortOrder = lastSortOrder;
                    }

                    lastColumn = e.ColumnToSort;
                    lastSortOrder = e.SortOrder;
                }

#if DEBUG
                System.Diagnostics.Debug.WriteLine(new string('+', 20));
                System.Diagnostics.Debug.WriteLine($"LastSort: {olvFiles?.LastSortColumn?.Text ?? "null"} {olvFiles.LastSortOrder}\nToSort: {e.ColumnToSort?.Text ?? "null"} {e.SortOrder}\nSecondaryToSort: {e.SecondaryColumnToSort?.Text ?? "null"} {e.SecondarySortOrder}");
#endif
            };

            kryptonTBPath.Text = settings.ModelsPath;

            olvFiles.PrimarySortColumn = ColumnFromType(settings.PrimarySortColumn);
            olvFiles.PrimarySortOrder = settings.PrimarySortOrder;
            olvFiles.SecondarySortColumn = ColumnFromType(settings.SecondarySortColumn);
            olvFiles.SecondarySortOrder = settings.SecondarySortOrder;
            //olvFiles.Sort(ColumnFromType(settings.PrimarySortColumn), settings.PrimarySortOrder);

            LoadModelsPath(kryptonTBPath.Text);

            FormClosing += (s, e) =>
            {
                if (e.CloseReason == CloseReason.UserClosing)
                {
                    if (Busy)
                    {
                        e.Cancel = true;
                    }
                    else
                    {
                        fileSystemWatcher1.EnableRaisingEvents = false;

                        settings.CurrentVAE = kryptonCMBModels.SelectedItem as string;
                        settings.ModelsPath = currentPath;
                        settingsMan.SaveSettings(settings);
                        ModelHasherState.State.SaveSettings();
                        closing = true;
                    }
                }
            };
        }

        ///==================================================================================
        /// FileSystemWatcher Handlers
        ///==================================================================================
        private void fileSystemWatcher1_Created(object sender, FileSystemEventArgs e)
        {
            if (this == null || Disposing || IsDisposed || initializing)
                return;

#if DEBUG
            System.Diagnostics.Debug.WriteLine($"{e.ChangeType} - {e.Name}");
#endif
            EntryBase entry = EntryBase.GetEntry(e.FullPath, new DirectoryInfo(currentPath).FullName);
            if (entry is EntryModel model)
            {
                olvFiles.SuspendLayout();
                olvFiles.BeginUpdate();

                AddModel(model);
                //olvFiles.Sort();
                //olvFiles.RefreshObject(model);

                olvFiles.ResumeLayout();
                olvFiles.EndUpdate();
            }
            else if (entry is EntryVAE vae)
            {
                AddVAE(vae);
                olvFiles.RefreshObject(vae.LinkedModel);
            }

            UpdateStatusStrip();
        }

        private void fileSystemWatcher1_Deleted(object sender, FileSystemEventArgs e)
        {
            if (this == null || Disposing || IsDisposed || initializing)
                return;

#if DEBUG
            System.Diagnostics.Debug.WriteLine($"{e.ChangeType} - {e.Name}");
#endif
            EntryBase entry = GetEntryByFullName(e.FullPath);
            if (entry is EntryModel model)
            {
                RemoveModel(model);
            }
            else if (entry is EntryVAE vae)
            {
                EntryModel entryModel = vae.LinkedModel;
                RemoveVAE(vae);
                olvFiles.RefreshObject(entryModel);
            }

            UpdateStatusStrip();
        }

        private void fileSystemWatcher1_Renamed(object sender, RenamedEventArgs e)
        {
            if (this == null || Disposing || IsDisposed || initializing)
                return;
#if DEBUG
            System.Diagnostics.Debug.WriteLine($"{e.ChangeType} - {e.OldName} -> {e.Name}");
#endif
            EntryBase entry = GetEntryByFullName(e.OldFullPath);
            if (entry is EntryModel model)
            {
                olvFiles.SuspendLayout();
                olvFiles.BeginUpdate();

                model.Renamed(e.FullPath);

                model.Link(GetVAEByModel(model));
                olvFiles.RefreshObject(model);
                //olvFiles.Sort();

                olvFiles.ResumeLayout();
                olvFiles.EndUpdate();
            }
            else if (entry is EntryVAE vae)
            {
                if (!vae.IsSymLink)
                    kryptonCMBModels.Items.Remove(vae.FileName);

                vae.Renamed(e.FullPath);

                if (!vae.IsSymLink)
                    kryptonCMBModels.Items.Add(vae.FileName);

                EntryModel oldModel = vae.LinkedModel;
                vae.Link(GetModelByVAE(vae));
                olvFiles.RefreshObject(oldModel);
                olvFiles.RefreshObject(vae.LinkedModel);
            }
            else
            {
                entry = EntryBase.GetEntry(e.FullPath, new DirectoryInfo(currentPath).FullName);
                if (entry is EntryModel model2)
                {
                    olvFiles.SuspendLayout();
                    olvFiles.BeginUpdate();

                    AddModel(model2);
                    //olvFiles.Sort();
                    UpdateStatusStrip();

                    olvFiles.ResumeLayout();
                    olvFiles.EndUpdate();
                }
                else if (entry is EntryVAE vae2)
                {
                    AddVAE(vae2);
                    olvFiles.RefreshObject(vae2.LinkedModel);
                }
            }
        }

        ///==================================================================================
        /// ContextMenu Handlers
        ///==================================================================================
        private void ctxFiles_Opening(object sender, CancelEventArgs e)
        {
            //bool allSymLink = true;
            //bool noneSymLink = true;
            int? groupCount = olvFiles?.OLVGroups?.Count;
            bool hasCollapsedGroups = false;
            bool hasExpandedGroups = false;
            bool hasAnySymLinkedVAE = false;
            bool hasNonSymLinkedVAE = false;
            bool hasAnyEmptySymlinkedVAE = false;

            if (groupCount.HasValue && olvFiles.ShowGroups)
            {
                for (int i = 0; i < groupCount; i++)
                {
                    if (olvFiles.OLVGroups[i].Collapsed)
                        hasCollapsedGroups = true;
                    else
                        hasExpandedGroups = true;

                    if (hasCollapsedGroups && hasExpandedGroups)
                        break;
                }
            }

            var files = tolvFiles.SelectedObjects;
            if (files.Count > 0)
            {
                for (int i = 0; i < files.Count; i++)
                {
                    EntryModel model = files[i];

                    if (model.HasSymLinkedVAE)
                        hasAnySymLinkedVAE = true;
                    else
                        hasAnyEmptySymlinkedVAE = true;

                    if (model.HasNonSymLinkedVAE)
                        hasNonSymLinkedVAE = true;
                }
            }
            else if(!hasCollapsedGroups && !hasExpandedGroups)
            {
                e.Cancel = true;
                return;
            }

            collapseAllToolStripMenuItem.Visible = hasExpandedGroups;
            expandAllToolStripMenuItem.Visible = hasCollapsedGroups;
            groupToolStripMenuItem.Visible = hasCollapsedGroups || hasExpandedGroups;

            bool canCreateSymLink = !hasNonSymLinkedVAE && hasAnyEmptySymlinkedVAE && kryptonCMBModels.SelectedIndex != -1;
            bool canRecreateSymLink = !hasNonSymLinkedVAE && hasAnySymLinkedVAE && kryptonCMBModels.SelectedIndex != -1;
            deleteSymLinkToolStripMenuItem.Visible = hasAnySymLinkedVAE;//!hasNonSymLinkedVAE && allSymLink;
            createSymLinkedVAEToolStripMenuItem.Visible = canCreateSymLink || canRecreateSymLink;
            createSymLinkedVAEToolStripMenuItem.Text = canRecreateSymLink ? (canCreateSymLink ? "Assign Symlink" : "Switch Symlink") : "Create Symlink";

            bool shiftDown = ModifierKeys.HasFlag(Keys.Shift);
            hashToolStripMenuItem.Text = shiftDown ? "Hash (Forced)" : "Hash";
            hashToolStripMenuItem.Visible = files.Count > 0;
            copyToolStripMenuItem.Text = shiftDown ? "Copy (+ Hash)" : "Copy";
            copyToolStripMenuItem.Visible = files.Count > 0;
        }

        private void createSymLinkedVAEToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (CheckForLackingAdmin())
                return;

            var files = tolvFiles.SelectedObjects;
            List<EntryModel> symlinks = files.Where((m) => !m.HasNonSymLinkedVAE).ToList();
            List<EntryModel> recreate = symlinks.Where((m) => m.HasSymLinkedVAE).ToList();
            bool doRecreate = true;
            if (recreate.Count > 0)
            {
                doRecreate = KryptonMessageBox.Show(this, $"You are about to recreate symbolic links for these models, are you sure?\n{string.Join("\n", recreate.Select((i) => i.FileName).Take(10))}{(recreate.Count <= 10 ? "" : $"\n{recreate.Count - 10} more...")}", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes;
            }

            if (doRecreate)
            {
                EntryVAE vae = vaeEntries.Find((v) => v.FileName.Equals(kryptonCMBModels.SelectedItem));
                for (int i = symlinks.Count - 1; i >= 0; i--)
                {
                    EntryModel model = symlinks[i];
                    if (model.HasSymLinkedVAE)
                    {
                        File.Delete(files[i].LinkedVAE.FullName);
                        RemoveVAE(files[i].LinkedVAE);
                    }

                    string simLinkPath = Path.Combine(Path.GetDirectoryName(model.FullName), model.FileNameNoExtension + ".vae" + vae.Extension);
                    SymLinkUtil.CreateSymbolicLink(simLinkPath, vae.FullName, SymLinkUtil.SYMBOLIC_LINK_FLAG.File);
                }
            }
        }

        private void deleteSymLinkToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var files = tolvFiles.SelectedObjects;
            List<string> symlinks = files.Where((m) => m.HasSymLinkedVAE).Select((m) => m.LinkedVAE.FileName).ToList();
            if (KryptonMessageBox.Show(this, $"You are about to remove these symbolic links, are you sure?\n{string.Join("\n", symlinks.Take(10))}{(symlinks.Count <= 10 ? "" : $"\n{symlinks.Count - 10} more...")}", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
            {
                for (int i = files.Count - 1; i >= 0; i--)
                {
                    if (files[i].HasSymLinkedVAE)
                    {
                        File.Delete(files[i].LinkedVAE.FullName);
                        RemoveVAE(files[i].LinkedVAE);
                    }
                }
                olvFiles.RefreshObjects((List<EntryModel>)files);
            }
        }

        private void copyToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CopySelection(ModifierKeys.HasFlag(Keys.Shift));
        }

        private async void hashToolStripMenuItem_Click(object sender, EventArgs e)
        {
            bool shiftDown = ModifierKeys.HasFlag(Keys.Shift);

            lock (hashingLock)
            {
                List<EntryModel> enqueue = new List<EntryModel>(tolvFiles.SelectedObjects.Where(m => shiftDown || !m.HashHash));
                for (int i = 0; i < enqueue.Count; i++)
                {
                    hashingQueue.Enqueue(new Tuple<EntryModel, bool>(enqueue[i], shiftDown));
                }

                if (Busy)
                    return;
                
                Busy = true;
            }

            DateTime lastTime = DateTime.UtcNow;
            long lastBytesRead = 0L;
            void progress_Changed(object s, HashingUtil.HasherProgress l)
            {
                DateTime now = DateTime.UtcNow;
                TimeSpan deltaTime = now - lastTime;
                if (deltaTime.TotalSeconds >= 1)
                {
                    float fP = ((float)l.TotalBytesRead / l.TotalBytes) * 100;
                    long deltaBytes = l.TotalBytesRead - lastBytesRead;
                    float bytesPerSec = (float)Math.Floor(deltaBytes / deltaTime.TotalSeconds);
                    toolStripProgressBar1.Value = (int)fP;
                    toolStripProgressBar1.DisplayValue = $"{hashingQueue.Count + 1} - {fP:0.0}% - {InternetUtil.FormatBytesPerSec(bytesPerSec)}";
                    lastBytesRead = l.TotalBytesRead;
                    lastTime = now;
                }
            }

            var progress = new Progress<HashingUtil.HasherProgress>();
            progress.ProgressChanged += progress_Changed;

            do
            {
                if(hashingQueue.TryDequeue(out Tuple<EntryModel, bool> item))
                {
                    var model = item.Item1;
                    var forced = item.Item2;
                    ModelHash hash = await Task.Run(() => ModelHasherState.State.UpdateStateFromPathAsync(model.FullName, model.RootDirectory, progress, forced));
                    model.Hash = hash.ShortHash;
                    olvFiles.RefreshObject(model);
                } 

                lock (hashingLock)
                {
                    if (hashingQueue.Count > 0)
                        continue;

                    progress.ProgressChanged -= progress_Changed;
                    toolStripProgressBar1.Value = 0;
                    toolStripProgressBar1.DisplayValue = "";
                    Busy = false;
                    break;
                }
            } while (true);
        }

        private void collapseAllToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SetAllGroupsCollapsed(true);
        }

        private void expandAllToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SetAllGroupsCollapsed(false);
        }

        ///==================================================================================
        /// Path and Browse Handlers
        ///==================================================================================
        private void kryptonTBPath_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                if (LoadModelsPath(kryptonTBPath.Text))
                {
                    e.SuppressKeyPress = true;
                }
            }
        }

        private void kryptonBTNBrowse_Click(object sender, EventArgs e)
        {
            FolderSelectDialog folderSelect = new FolderSelectDialog();
            if (folderSelect.ShowDialog() && !IsDisposed)
            {
                kryptonTBPath.Text = folderSelect.FileName;
                LoadModelsPath(kryptonTBPath.Text);
            }
        }
        ///==================================================================================
        /// ObjectListView Handlers
        ///==================================================================================
        private void olvFiles_KeyDown(object sender, KeyEventArgs e)
        {
            if(e.Control && e.KeyCode == Keys.C && CopySelection(ModifierKeys.HasFlag(Keys.Shift)))
            {
                e.SuppressKeyPress = true;
            }
            else if(e.KeyCode == Keys.Escape)
            {
                olvFiles.DeselectAll();
                e.SuppressKeyPress = true;
            }
        }

        private void olvFiles_SelectedIndexChanged(object sender, EventArgs e)
        {
            UpdateStatusStrip();
        }

        ///==================================================================================
        /// Private Methods
        ///==================================================================================
        private void SetAllGroupsCollapsed(bool collapsed)
        {
            for (int i = 0; i < olvFiles.OLVGroups.Count; i++)
            {
                if (olvFiles.OLVGroups[i].Collapsed != collapsed)
                    olvFiles.OLVGroups[i].Collapsed = collapsed;
            }
        }

        private void UpdateStatusStrip()
        {
            int modelCount = olvFiles.Items.Count;
            int selectionCount = olvFiles.SelectedIndices.Count;

            toolStripStatusLabel1.Text = $"Models: {(selectionCount == 0 ? "" : $"{selectionCount}/")}{olvFiles.Items.Count}";
        }

        private bool LoadModelsPath(string path)
        {
            bool result = false;

            initializing = true;
            fileSystemWatcher1.EnableRaisingEvents = false;

            modelEntries.Clear();
            vaeEntries.Clear();

            olvFiles.BeginUpdate();
            olvFiles.SuspendLayout();
            kryptonCMBModels.SuspendLayout();

            olvFiles.ClearObjects();
            kryptonCMBModels.Items.Clear();

            resolveMissingHashes = true;

            if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path))
            {
                currentPath = path;
                string root = new DirectoryInfo(path).FullName;
                string[] files = Directory.GetFiles(path, "*", SearchOption.AllDirectories);
                for (int i = 0; i < files.Length; i++)
                {
                    EntryBase entry = EntryBase.GetEntry(files[i], root);
                    if (entry is EntryModel model)
                        modelEntries.Add(model);
                    else if (entry is EntryVAE vae)
                        vaeEntries.Add(vae);
                }

                for (int i = 0; i < vaeEntries.Count; i++)
                {
                    ProcessVAE(vaeEntries[i]);
                }

                olvFiles.AddObjects(modelEntries);
                olvFiles.Sort();

                if (kryptonCMBModels.Items.Count > 0)
                {
                    string current = settings.CurrentVAE;
                    if (!string.IsNullOrEmpty(current))
                    {
                        for (int i = 0; i < kryptonCMBModels.Items.Count; i++)
                        {
                            if (current.Equals(kryptonCMBModels.Items[i] as string, StringComparison.InvariantCultureIgnoreCase))
                            {
                                kryptonCMBModels.SelectedIndex = i;
                                break;
                            }
                        }
                    }
                    else
                    {
                        kryptonCMBModels.SelectedIndex = 0;
                    }
                }

                fileSystemWatcher1.Path = path;
                fileSystemWatcher1.EnableRaisingEvents = true;
                result = true;
            }

            olvFiles.EndUpdate();
            olvFiles.ResumeLayout();
            kryptonCMBModels.ResumeLayout();

            initializing = false;

            UpdateStatusStrip();
            pbPath.Image = pathState[result];

            return result;
        }

        private void AddVAE(EntryVAE vae)
        {
            vaeEntries.Add(vae);
            ProcessVAE(vae);
        }

        private void RemoveVAE(EntryVAE vae)
        {
            vaeEntries.Remove(vae);
            vae.Link(null);
            if (!vae.IsSymLink)
                kryptonCMBModels.Items.Remove(vae.FileName);
        }


        private void ProcessVAE(EntryVAE vae)
        {
            if (!vae.IsSymLink)
                kryptonCMBModels.Items.Add(vae.FileName);

            for (int j = 0; j < modelEntries.Count; j++)
            {
                if (vae.MatchesModel(modelEntries[j]))
                {
                    vae.Link(modelEntries[j]);
                    break;
                }
            }
        }

        private void AddModel(EntryModel model)
        {
            lock (addremoveLock)
            {
                modelEntries.Add(model);

                for (int i = 0; i < vaeEntries.Count; i++)
                {
                    if (vaeEntries[i].MatchesModel(model))
                    {
                        model.Link(vaeEntries[i]);
                        break;
                    }
                }

                olvFiles.AddObject(model);
            }
        }

        private void RemoveModel(EntryModel model)
        {
            lock (addremoveLock)
            {
                modelEntries.Remove(model);
                model.Link(null);
                olvFiles.RemoveObject(model);
            }
        }

        private EntryModel GetModelByFullName(string fullName)
        {
            EntryModel result = null;

            for (int i = 0; i < modelEntries.Count; i++)
            {
                if (modelEntries[i].FullName.Equals(fullName, StringComparison.InvariantCultureIgnoreCase))
                {
                    result = modelEntries[i];
                    break;
                }
            }

            return result;
        }

        private EntryVAE GetVAEByFullName(string fullName)
        {
            EntryVAE result = null;

            for (int i = 0; i < vaeEntries.Count; i++)
            {
                if (vaeEntries[i].FullName.Equals(fullName, StringComparison.InvariantCultureIgnoreCase))
                {
                    result = vaeEntries[i];
                    break;
                }
            }

            return result;
        }

        private EntryBase GetEntryByFullName(string fullName)
        {
            EntryBase result = null;

            EntryBase.FileType fileType = EntryBase.GetFileType(fullName);

            if (fileType == EntryBase.FileType.Model)
                result = GetModelByFullName(fullName);
            else if (fileType == EntryBase.FileType.Vae)
                result = GetVAEByFullName(fullName);

            return result;
        }

        private EntryModel GetModelByVAE(EntryVAE vae)
        {
            EntryModel result = null;
            for (int i = 0; i < modelEntries.Count; i++)
            {
                if (vae.MatchesModel(modelEntries[i]))
                {
                    result = modelEntries[i];
                    break;
                }
            }
            return result;
        }

        private EntryVAE GetVAEByModel(EntryModel model)
        {
            EntryVAE result = null;
            for (int i = 0; i < vaeEntries.Count; i++)
            {
                if (model.MatchesVAE(vaeEntries[i]))
                {
                    result = vaeEntries[i];
                    break;
                }
            }
            return result;
        }

        private bool CheckForLackingAdmin()
        {
            if (!ElevateUtil.IsRunningAsAdministrator)
            {
                KryptonMessageBox.Show(this, "Restart with admin privileges to be able to create symlinks", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return true;
            }
            return false;
        }

        private bool CopySelection(bool shiftDown = false)
        {
            var files = tolvFiles.SelectedObjects;
            if (files.Count != 0)
            {
                ClipboardUtil.SetText(string.Join(ControlChars.CrLf, files.Select(f => (shiftDown && !string.IsNullOrEmpty(f.Hash) ? $"{f.Hash} " : "") + f.PathName + f.FileNameNoExtension)));
            }
            return files.Count > 0;
        }

        private OLVColumn ColumnFromType(ColumnType type)
        {
            switch (type)
            {
                case ColumnType.Symlink:
                    return olvColumnSymLink;
                case ColumnType.Path:
                    return olvColumnPath;
                case ColumnType.Filename:
                    return olvColumnName;
                case ColumnType.VAE:
                    return olvColumnVAE;
                case ColumnType.Invalid:
                default:
                    return null;
            }
        }

        private ColumnType TypeFromColumn(OLVColumn column)
        {
            switch (column.Text)
            {
                case "Sym":
                    return ColumnType.Symlink;
                case "Path":
                    return ColumnType.Path;
                case "Name":
                    return ColumnType.Filename;
                case "VAE":
                    return ColumnType.VAE;
                default:
                    return ColumnType.Invalid;
            }
        }

        ///==================================================================================
        /// Public Methods
        ///==================================================================================
        public bool CloseFromTaskTray()
        {
            if (Disposing || IsDisposed || !Visible)
                return true;

            Close();
            return closing;
        }
    }
}
